package com.shuwen.gcdj.common.config;

import com.shuwen.gcdj.common.annotation.Auth;
import com.shuwen.gcdj.common.annotation.OpAuth;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.ParameterBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.ApiKey;
import springfox.documentation.service.AuthorizationScope;
import springfox.documentation.service.Contact;
import springfox.documentation.service.Parameter;
import springfox.documentation.service.SecurityReference;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.ApiSelectorBuilder;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.lang.annotation.Annotation;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Configuration
@EnableSwagger2
public class SwaggerConfig implements WebMvcConfigurer {
    @Value("${swagger.enable}")
    private boolean enableSwagger;

    @Value("${swagger.api.host}")
    private String apiHost;

    private static final String PACKAGE_PATH = "com.shuwen.gcdj";

    private static final String HEADER = "header";

    private static final String CONTENT_TYPE = "content-type";

    private static final String STRING = "string";

    private static final String JSON = "application/json;charset=UTF-8";

    private static final String TOKEN_NAME = "eceibstoken";

    private static final String RESOURCES = "classpath:/META-INF/resources/";

    /**
     * 内部前端调用接口
     */
    @Bean
    public Docket createInnerApi() {
        //添加head参数start
        List<Parameter> pars = new ArrayList<>();

        pars.add(createTokenPar(CONTENT_TYPE, "数据类型", JSON, false));

        pars.add(createTokenPar(TOKEN_NAME, "令牌", null, true));

        pars.add(createTokenPar("powercode", "所属页面code", null, false));

        //添加head参数end
        return createDocket("innerApi", pars, Auth.class, tokenSecuritySchemes(), securityContexts());
    }

    /**
     * 创建请求头
     *
     * @param name         name
     * @param description  description
     * @param defaultValue defaultValue
     * @param required     required
     */
    private Parameter createTokenPar(String name, String description, String defaultValue, boolean required) {
        ParameterBuilder tokenPar = new ParameterBuilder();
        tokenPar.name(name).description(description).defaultValue(defaultValue).modelRef(new ModelRef(STRING))
                .parameterType(HEADER).required(required);
        return tokenPar.build();
    }

    /**
     * 外部访问接口
     */
    @Bean
    public Docket createOutApi() {
        //添加head参数start
        List<Parameter> pars = new ArrayList<>();

        //添加head参数end
        return createDocketFeign("对外开放接口", pars, OpAuth.class, akskSecuritySchemes(), securityContexts());
    }

    /**
     * 前端访问接口
     */
    @Bean
    public Docket createManagerApi() {
        //添加head参数start
        List<Parameter> pars = new ArrayList<>();

        pars.add(createTokenPar(CONTENT_TYPE, "数据类型", JSON, false));

        pars.add(createTokenPar(TOKEN_NAME, "令牌", null, false));

        pars.add(createTokenPar("powercode", "所属页面code", null, false));

        //添加head参数end
        return createDocket("前端访问接口", pars, null, null, null);
    }

    /**
     * 创建docket
     *
     * @param groupName        groupName
     * @param pars             pars
     * @param apiKeys          apiKeys
     * @param securityContexts securityContexts
     */
    private Docket createDocket(String groupName, List<Parameter> pars, Class<? extends Annotation> annotationClazz,
                                List<ApiKey> apiKeys, List<SecurityContext> securityContexts) {
        ApiSelectorBuilder apiSelectorBuilder = new Docket(DocumentationType.SWAGGER_2)
                .directModelSubstitute(LocalDateTime.class, Date.class)
                .directModelSubstitute(LocalDate.class, Date.class).directModelSubstitute(LocalTime.class, Date.class)
                .host(apiHost).apiInfo(apiInfo()).enable(enableSwagger).select()
                .apis(RequestHandlerSelectors.basePackage(PACKAGE_PATH)).paths(PathSelectors.any());
        if (annotationClazz != null) {
            apiSelectorBuilder.apis(RequestHandlerSelectors.withMethodAnnotation(annotationClazz));
        }
        Docket docket = apiSelectorBuilder.build();
        if (StringUtils.isNotBlank(groupName)) {
            docket.groupName(groupName);
        }
        if (apiKeys == null) {
            docket.globalOperationParameters(pars);
        }
        if (apiKeys != null) {
            docket.securitySchemes(apiKeys);
        }
        if (securityContexts != null) {
            docket.securityContexts(securityContexts);
        }
        return docket;
    }

    /**
     * 创建docket
     *
     * @param groupName        groupName
     * @param pars             pars
     * @param apiKeys          apiKeys
     * @param securityContexts securityContexts
     */
    private Docket createDocketFeign(String groupName, List<Parameter> pars, Class<? extends Annotation> annotationClazz,
                                List<ApiKey> apiKeys, List<SecurityContext> securityContexts) {
        ApiSelectorBuilder apiSelectorBuilder = new Docket(DocumentationType.SWAGGER_2)
                .directModelSubstitute(LocalDateTime.class, Date.class)
                .directModelSubstitute(LocalDate.class, Date.class).directModelSubstitute(LocalTime.class, Date.class)
                .host(apiHost).apiInfo(apiInfo()).enable(enableSwagger).select()
                .apis(RequestHandlerSelectors.basePackage(PACKAGE_PATH)).paths(PathSelectors.any());
        if (annotationClazz != null) {
            apiSelectorBuilder.apis(RequestHandlerSelectors.withMethodAnnotation(annotationClazz));
        }
        Docket docket = apiSelectorBuilder.build();
        if (StringUtils.isNotBlank(groupName)) {
            docket.groupName(groupName);
        }
        if (apiKeys != null) {
            docket.globalOperationParameters(pars);
        }
        if (apiKeys != null) {
            docket.securitySchemes(apiKeys);
        }
        if (securityContexts != null) {
            docket.securityContexts(securityContexts);
        }
        return docket;
    }

    /**
     * 创建该API的基本信息（这些基本信息会展现在文档页面中）
     * 访问地址：http://项目实际地址/swagger-ui.html
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder().title("OP-API").description("接口文档").termsOfServiceUrl("").version("2.0")
                .contact(new Contact("李东旭", null, null)).build();
    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("").addResourceLocations("classpath:/index.html");
        registry.addResourceHandler("swagger-ui.html").addResourceLocations(RESOURCES);
        registry.addResourceHandler("doc.html").addResourceLocations(RESOURCES);
        registry.addResourceHandler("api-docs").addResourceLocations(RESOURCES);
        registry.addResourceHandler("/webjars/**").addResourceLocations(RESOURCES + "webjars/");
    }


    /**
     * 提升MappingJackson2HttpMessageConverter的优先级
     * 解决 ResponseBodyAdvice String 问题
     * @param converters converters
     */
    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {

        converters.add(0, new MappingJackson2HttpMessageConverter());
    }

    private List<ApiKey> tokenSecuritySchemes() {
        List<ApiKey> apiKeyList = new ArrayList<>();
        apiKeyList.add(new ApiKey(TOKEN_NAME, TOKEN_NAME, HEADER));
        return apiKeyList;
    }

    private List<ApiKey> akskSecuritySchemes() {
        List<ApiKey> apiKeyList = new ArrayList<>();
        return apiKeyList;
    }

    private List<SecurityContext> securityContexts() {
        List<SecurityContext> securityContexts = new ArrayList<>();
        securityContexts.add(SecurityContext.builder().securityReferences(defaultAuth())
                .forPaths(PathSelectors.regex("^(?!auth).*$")).build());
        return securityContexts;
    }

    private List<SecurityReference> defaultAuth() {
        AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
        AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
        authorizationScopes[0] = authorizationScope;
        List<SecurityReference> securityReferences = new ArrayList<>();
        securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
        return securityReferences;
    }
}
